package org.marsdonne.commons;

import lombok.Getter;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public interface DateCursorPageable<T extends VersionedIdentifiable> extends JpaCursorPageable<T> {
    @Getter
    class DateCursor extends Cursor {
        public DateCursor(Date start, Date end) {
            super();
            this.start = start == null ? 0 : start.getTime();
            this.end = end == null ? 0 : end.getTime();
        }
    }

    @Override
    default Cursor getCursor(EntityManager entityManager, Specification<T> spec, @Nullable Long after) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<DateCursor> criteriaQuery = criteriaBuilder.createQuery(DateCursor.class);
        Root<T> root = criteriaQuery.from(getGenericType());
        root.alias("cursors");

        criteriaQuery.multiselect(
                criteriaBuilder.min(root.get(getCursorFieldName())).alias("start"),
                criteriaBuilder.max(root.get(getCursorFieldName())).alias("end")
        );

        criteriaQuery.where(toPredicates(criteriaBuilder, criteriaQuery, root, after, spec));

        TypedQuery<DateCursor> query = entityManager.createQuery(criteriaQuery);

        return query.getSingleResult();
    }

    @Override
    default Predicate[] toPredicates(CriteriaBuilder criteriaBuilder, CriteriaQuery criteriaQuery, Root<T> root, Long after, Specification<T> spec) {
        List<Predicate> predicateList = new ArrayList<>();
        if (after != null) {
            predicateList.add(criteriaBuilder.lessThan(root.get(getCursorFieldName()), new Date(after.longValue())));
        }

        if (spec != null) {
            predicateList.add(spec.toPredicate(root, criteriaQuery, criteriaBuilder));
        }

        return predicateList.toArray(new Predicate[0]);
    }
}
